// Created: Dec 11, 2014
//
// Author: wjinwen.1988@gmail.com

#include "chi/directory.h"

#include <libgen.h>
#include <dirent.h>
#include <sys/types.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#include <iostream>

namespace chi {
static char* strip_tail_dir_slashes(char* fname, const int len) {
  if (!fname) return NULL;

  int i = len - 1;
  if (len > 1) {
    while (fname[i] == '/')
      fname[i--] = '\0';
  }
  return fname;
}

bool Directory::Exists(const char* filename) {
  if (filename == NULL) return false;

  int len = strlen(filename);
  if (len > kMaxPathLength || len < 0) return false;

  struct stat file_info;
  char path[kMaxPathLength+1];
  strncpy(path, filename, len);
  path[len] = '\0';
  char* copy = strip_tail_dir_slashes(path, len);
  int32_t result = stat(copy, &file_info);
  return result == 0;
}

bool Directory::DeleteFile(const char* filename) {
  if (!filename) return false;

  bool ret = true;
  bool exist = true;

  if (!Exists(filename)) exist = false;

  if (exist) {
    if (IsDirectory(filename))
      ret = false;
  }

  if (ret && exist) {
    if (unlink(filename) == -1) {
      std::cout << "unlink file: " << filename
                << " errno: " << errno << std::endl;
      ret = false;
    }
  }
  return !exist ? true : ret;
}

bool Directory::DeleteDirectoryRecursively(const char* directory,
                                           const bool delete_flag) {
  if (!directory) return false;

  struct dirent dirent;
  struct dirent* result = NULL;
  DIR* dir = NULL;
  bool ret = true;
  dir = opendir(directory);
  if (!dir) ret = false;

  while (ret && !readdir_r(dir, &dirent, &result) && result) {
    char* name = result->d_name;
    if (strcmp(name, ".") == 0 ||
        strcmp(name, "..") == 0 ||
        strcmp(name, "lost_found") == 0) continue;

    char path[kMaxPathLength];
    snprintf(path, kMaxPathLength, "%s%c%s", directory, '/', name);
    if (IsDirectory(path)) {
      if (!DeleteDirectoryRecursively(path, true)) {
        ret = false;
        break;
      }
    } else {
      if (!DeleteFile(path)) {
        ret = false;
        break;
      }
    }
  } // end while

  if (dir != NULL)
    closedir(dir);

  if (!delete_flag) {
    return ret;
  } else {
    return ret ? DeleteDirectory(directory) : ret;
  }
}

bool Directory::IsDirectory(const char* dirname) {
  if (!dirname) return false;

  int dir_len = strlen(dirname);
  if (dir_len > kMaxPathLength || dir_len <= 0)
    return false;

  struct stat file_info;
  char path[kMaxPathLength];
  strncpy(path, dirname, dir_len);
  path[dir_len] = '\0';
  char* copy = strip_tail_dir_slashes(path, dir_len);
  int result = stat(copy, &file_info);

  return result < 0 ? false : S_ISDIR(file_info.st_mode);
}

bool Directory::DeleteDirectory(const char* dirname) {
  if (!dirname) return false;

  bool ret = true;
  bool isexist = true;
  if (!Exists(dirname))
    isexist = false;

  if (isexist) {
    if (!IsDirectory(dirname))
      ret = false;
  }

  if (ret && isexist) {
    if (rmdir(dirname) != 0)
      ret = false;
  }
  return !isexist ? true : ret;
}

bool Directory::CreateDirectory(const char* dirname,
                                const mode_t dir_mode) {
  if (!dirname) return false;

  mode_t umake_value = umask(0);
  umask(umake_value);
  mode_t mode = (S_IRWXUGO & (~umake_value)) | S_IWUSR | S_IXUSR;
  bool ret = false;

  if (mkdir(dirname, mode) == 0 && // create directory success
      (dir_mode == 0 ||            // not chmod
       chmod(dirname, dir_mode) == 0)) {
    ret = true;
  }

  if (!ret && EEXIST == errno)
    ret = true;
  return ret;
}

// creats the full path of fullpath, return true on success
bool Directory::CreateFullPath(const char* fullpath,
                               const bool with_file,
                               const mode_t dir_mode) {
  if (!fullpath) return false;

  int32_t len = strlen(fullpath);
  if (len > kMaxPathLength || len <= 0)
    return false;

  char dirpath[kMaxPathLength];
  strncpy(dirpath, fullpath, len);
  dirpath[len] = '\0';
  char* path = dirpath;

  if (with_file)
    path = dirname(dirpath);

  bool ret = true;
  struct stat stats;
  if ((lstat(path, &stats) != 0) || !S_ISDIR(stats.st_mode)) {
    while (*path == '/') path++;

    while (ret) {
      path = strchr(path, '/');
      if (!path) break;

      *path = '\0';

      if (!IsDirectory(dirpath)) {
        if (!CreateDirectory(dirpath, dir_mode)) {
          ret = false;
          break;
        }
      }
      *path++ = '/';

      while (*path == '/')
        path++;
    }

    if (ret) {
      if (!IsDirectory(dirpath)) {
        if (!CreateDirectory(dirpath, dir_mode)) 
          ret = true;
      }
    }
  }
  return ret;
}

bool Directory::Rename(const char* srcfilename,
                       const char* destfilename) {
  if (!srcfilename || !destfilename)
    return false;

  bool ret = true;
  if (rename(srcfilename, destfilename) != 0)
    ret = false;
  return ret;
}

int64_t Directory::Size(const char* filename) {
  if (!filename) return 0;

  int64_t ret = 0;
  bool is_exists = true;
  if (!Exists(filename))
    is_exists = false;

  if (is_exists) {
    if (IsDirectory(filename))
      is_exists = false;
  }

  if (is_exists) {
    struct stat file_info;
    if (stat(filename, &file_info) == 0)
      ret = file_info.st_size;
  }
  return ret;
}

}  // namespace chi

